package org.infodavid.persistence.impl.hibernate.usertype;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Serializable;
import java.sql.Blob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import javax.imageio.ImageIO;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;

/**
 * The Class AwtImageUserType.
 */
@SuppressWarnings("rawtypes")
public class AwtImageUserType implements UserType {

  /** The Constant LOGGER. */
  private static final Log LOGGER = LogFactory.getLog(AwtImageUserType.class);

  /** The default image format. */
  private static String defaultFormat = "png";

  /** The Constant SQL_TYPES. */
  private static final int[] SQL_TYPES = {
    Types.BLOB
  };

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#sqlTypes()
   */
  @Override
  public int[] sqlTypes() {
    return SQL_TYPES;
  }

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#returnedClass()
   */
  @Override
  public Class returnedClass() {
    return Image.class;
  }

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#equals(java.lang.Object, java.lang.Object)
   */
  @Override
  public boolean equals(final Object x, final Object y) throws HibernateException {
    final boolean result;

    if (x == y) {
      result = true;
    }
    else if (x == null || y == null) {
      result = false;
    }
    else {
      result = x.equals(y);
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet, java.lang.String[],
   * java.lang.Object)
   */
  public Object nullSafeGet(final ResultSet resultSet, final String[] names, final Object owner)
      throws HibernateException, SQLException {
    InputStream in = null;
    final Image result;
    Blob blob;

    try {
      blob = resultSet.getBlob(names[0]);

      if (blob == null) {
        result = null;
      }
      else {
        in = blob.getBinaryStream();
        result = ImageIO.read(in);
      }
    }
    catch (final IOException e) {
      throw new HibernateException("An error occurs while reading from stream", e);
    }
    finally {
      if (in != null) {
        try {
          in.close();
        }
        catch (final Exception e) {
          LOGGER.warn("An error occurs while closing stream", e);
        }
      }
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#nullSafeSet(java.sql.PreparedStatement, java.lang.Object,
   * int)
   */
  public void nullSafeSet(final PreparedStatement statement, final Object value, final int index)
      throws HibernateException, SQLException {
    final PipedInputStream in = new PipedInputStream();
    BufferedImage bufferedImage;
    PipedOutputStream out = null;

    try {
      if (value instanceof BufferedImage) {
        bufferedImage = (BufferedImage)value;
      }
      else if (value instanceof VolatileImage) {
        bufferedImage = ((VolatileImage)value).getSnapshot();
      }
      else {
        final Image img = (Image)value;
        bufferedImage =
            new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
        final Graphics2D g2 = bufferedImage.createGraphics();

        g2.drawImage(img, null, null);
        g2.dispose();
      }

      out = new PipedOutputStream(in);
      ImageIO.write(bufferedImage, defaultFormat, out);
      out.flush();
      statement.setBlob(index, in);
    }
    catch (final IOException e) {
      throw new HibernateException("An error occurs while writing to stream", e);
    }
    finally {
      if (out != null) {
        try {
          out.close();
        }
        catch (final Exception e) {
          LOGGER.warn("An error occurs while closing stream", e);
        }
      }

      try {
        in.close();
      }
      catch (final Exception e) {
        LOGGER.warn("An error occurs while closing stream", e);
      }
    }
  }

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#deepCopy(java.lang.Object)
   */
  @Override
  public Object deepCopy(final Object value) throws HibernateException {
    return value;
  }

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#isMutable()
   */
  @Override
  public boolean isMutable() {
    return true;
  }

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#assemble(java.io.Serializable, java.lang.Object)
   */
  @Override
  public Object assemble(final Serializable cached, final Object owner) throws HibernateException {
    return cached;
  }

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#disassemble(java.lang.Object)
   */
  @Override
  public Serializable disassemble(final Object value) throws HibernateException {
    return (Serializable)value;
  }

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#hashCode(java.lang.Object)
   */
  @Override
  public int hashCode(final Object x) throws HibernateException {
    return x == null ? 0 : x.hashCode();
  }

  /*
   * (non-Javadoc)
   * @see org.hibernate.usertype.UserType#replace(java.lang.Object, java.lang.Object,
   * java.lang.Object)
   */
  @Override
  public Object replace(final Object original, final Object target, final Object owner)
      throws HibernateException {
    final Object result = target;

    if (original instanceof Image && target instanceof Image) {
      final Graphics2D g2 = (Graphics2D)((Image)original).getGraphics();

      g2.drawImage((Image)target, null, null);
      g2.dispose();
    }

    return result;
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet, java.lang.String[],
   *      org.hibernate.engine.spi.SessionImplementor, java.lang.Object)
   */
  @Override
  public Object nullSafeGet(ResultSet arg0, String[] arg1, SessionImplementor arg2, Object arg3)
      throws HibernateException, SQLException {
    return nullSafeGet(arg0, arg1, arg3);
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.hibernate.usertype.UserType#nullSafeSet(java.sql.PreparedStatement, java.lang.Object,
   *      int, org.hibernate.engine.spi.SessionImplementor)
   */
  @Override
  public void nullSafeSet(PreparedStatement arg0, Object arg1, int arg2, SessionImplementor arg3)
      throws HibernateException, SQLException {
    nullSafeSet(arg0, arg1, arg2);
  }
}
